using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace Oops.Xna.Framework.Viewpoint
{
	/// <summary>
	/// Represents a collection of <see cref="Camera"/> instances keyed on the name of the camera.
	/// </summary>
	public class CameraCollection : KeyedGameComponentCollection<string, Camera>
	{
		/// <summary>
		/// Creates an instance of the <see cref="CameraCollection"/> class.
		/// </summary>
		internal CameraCollection() : base()
		{
		}

		#region KeyedCollection Members

		protected override string GetKeyForItem(Camera item)
		{
			// Key the collection by the name of the camera.
			return item.Name;
		}

		#endregion
	}

	/// <summary>
	/// Represents the game service for exposing and managing cameras.
	/// </summary>
	public class ViewpointComponent : GameComponent, IViewpointService
	{
		private bool initialized;
		private List<IGameComponent> notYetInitialized = new List<IGameComponent>();

		private CameraCollection cameras;
		private Camera activeCamera;

		private EventHandler cameraEnabledChangedEventHandler;

		/// <summary>
		/// Creates an instance of the <see cref="ViewpointComponent"/> class.
		/// </summary>
		/// <param name="game">The associated <see cref="Microsoft.Xna.Framework.Game"/>.</param>
		public ViewpointComponent(Game game) : base(game) 
		{
			// The game parameter must be specified.
			if (game == null)
			{
				throw new ArgumentNullException("game");
			}

			// Create the camera collection and subscribe to some of it's events.
			this.cameras = new CameraCollection();
			// TODO : GC!
			this.cameras.ComponentAdded += new EventHandler<GameComponentCollectionEventArgs<Camera>>(this.OnCameraAdded);
			this.cameras.ComponentRemoved += new EventHandler<GameComponentCollectionEventArgs<Camera>>(this.OnCameraRemoved);

			// Create some event handlers.  This is a garbage collection precaution.
			// TODO : GC!
			this.cameraEnabledChangedEventHandler = new EventHandler(this.OnCameraEnabledChanged);
		}

		#region IViewpointService Members 

		public Camera ActiveCamera
		{
			get { return this.activeCamera; }
		}

		public CameraCollection Cameras
		{
			get { return this.cameras; }
		}

		#endregion

		#region GameComponent Members

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				lock (this)
				{
					// Remove self from the service container.
					if (this.Game.GetService<IViewpointService>(false) == this)
					{
						this.Game.RemoveService<IViewpointService>();
					}

					// Dispose of all cameras.
					for (int i = this.cameras.Count - 1; i >= 0; i--)
					{
						IDisposable disposable = this.cameras[i] as IDisposable;
						if (disposable != null)
						{
							disposable.Dispose();
						}
					}

					// Unsubscribe to some camera collection events.
					// TODO : GC!
					this.cameras.ComponentAdded -= new EventHandler<GameComponentCollectionEventArgs<Camera>>(this.OnCameraAdded);
					this.cameras.ComponentRemoved -= new EventHandler<GameComponentCollectionEventArgs<Camera>>(this.OnCameraRemoved);
				}
			}

			// Call inherited method.
			base.Dispose(disposing);
		}

		#endregion

		#region IGameComponent Members

		public override void Initialize()
		{
			// Add self to the service container.
			this.Game.AddService<IViewpointService>(this);

			// Initialize all necessary behaviors.
			while (this.notYetInitialized.Count != 0)
			{
				this.notYetInitialized[0].Initialize();
				this.notYetInitialized.RemoveAt(0);
			}

			// Call inherited method.
			base.Initialize();

			// Set the initialized flag.
			this.initialized = true;
		}

		#endregion

		#region IUpdateable Members

		public override void Update(GameTime gameTime)
		{
			// Only update the active camera.
			if (this.activeCamera != null)
			{
				if (this.activeCamera.Enabled)
				{
					this.activeCamera.Update(gameTime);
				}
			}

			// Call inherited method.
			base.Update(gameTime);
		}

		#endregion

		private void OnCameraAdded(object sender, GameComponentCollectionEventArgs<Camera> e)
		{
			// Initialize or add the camera to the initialization pending list.
			if (this.initialized)
			{
				e.Item.Initialize();
			}
			else
			{
				this.notYetInitialized.Add(e.Item);
			}

			// Subscribe to some of the added camera's events and immediately process
			// the enabled state of the camera.
			e.Item.EnabledChanged += this.cameraEnabledChangedEventHandler;
			this.OnCameraEnabledChanged(e.Item, EventArgs.Empty);
		}

		private void OnCameraRemoved(object sender, GameComponentCollectionEventArgs<Camera> e)
		{
			// Remove the camera from the initialization pending list.
			if (!this.initialized)
			{
				this.notYetInitialized.Remove(e.Item);
			}

			// Unsubscribe from some of the removing camera's events.
			e.Item.EnabledChanged -= this.cameraEnabledChangedEventHandler;
		}

		private void OnCameraEnabledChanged(object sender, EventArgs e)
		{
			// Get the camera whose enabled status changed.
			Camera camera = (Camera)sender;

			// If the camera is enabled, disable all other cameras.
			if (camera.Enabled)
			{
				for (int i = 0; i < this.cameras.Count; i++)
				{
					if (this.cameras[i] != camera)
					{
						this.cameras[i].Enabled = false;
					}
				}

				// Hold onto the active camera.
				this.activeCamera = camera;
			}
			else
			{
				// Inactivating the active camera.
				//if (this.activeCamera == camera)
				//{
				//    this.activeCamera = null;
				//}
			}
		}
	}
}